/*----------------------------------------------------------------------*/
/* FatFs sample project for Atmel AVR                (C)ChaN, 2018      */
/*----------------------------------------------------------------------*/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <string.h>
#include "uart.h"
#include "xitoa.h"
#include "ff.h"
#include "diskio.h"
#include "rtc.h"
#include "sound.h"



FUSES = {0xF7, 0x91, 0xFC};		/* ATmega1284p fuses: Low, High, Extended. */
/* This is the fuse settings for this project. The fuse bits will be included
in the output hex file with program code. However some old flash programmers
cannot load the fuse bits from hex file. If it is the case, remove this line
and use these values to program the fuse bits. */


BYTE Buff[4096];	/* Working buffer */

FATFS FatFs[2];		/* Filesystem object for each logical drive */
FIL File[2];		/* File object */
DIR Dir;			/* Directory object */
FILINFO Finfo;

BYTE RtcOk;				/* RTC is available */
volatile UINT Timer;	/* Performance timer (100Hz increment) */



/*--------------------------------------------------------------------*/
/* User Provided Timer Function for FatFs module                      */
/*--------------------------------------------------------------------*/
/* This is a real time clock service to be called from FatFs module.  */
/* Any valid time must be returned even if the system does not        */
/* support a real time clock. This is not required when FatFs is      */
/* configured for FF_FS_READONLY or FF_FS_NORTC = 1.                  */
/*--------------------------------------------------------------------*/


DWORD get_fattime (void)
{
	RTC rtc;


	if (!RtcOk) return 0;

	/* Get local time */
	rtc_gettime(&rtc);

	/* Pack date and time into a DWORD variable */
	return	  ((DWORD)(rtc.year - 1980) << 25)
			| ((DWORD)rtc.month << 21)
			| ((DWORD)rtc.mday << 16)
			| ((DWORD)rtc.hour << 11)
			| ((DWORD)rtc.min << 5)
			| ((DWORD)rtc.sec >> 1);
}



/*---------------------------------------------------------*/
/* 100Hz timer interrupt generated by OC0A                 */
/*---------------------------------------------------------*/


ISR(TIMER0_COMPA_vect)
{
	Timer++;			/* Performance counter for this module */
	disk_timerproc();	/* Drive timer procedure of low level disk I/O module */
}



/*--------------------------------------------------------------------------*/
/* Monitor                                                                  */


static
void put_dump (const BYTE *buff, DWORD ofs, BYTE cnt)
{
	BYTE i;


	xprintf(PSTR("%08lX:"), ofs);

	for(i = 0; i < cnt; i++)
		xprintf(PSTR(" %02X"), buff[i]);

	xputc(' ');
	for(i = 0; i < cnt; i++)
		xputc((buff[i] >= ' ' && buff[i] <= '~') ? buff[i] : '.');

	xputc('\n');
}


static
void get_line (char *buff, int len)
{
	BYTE c;
	int i = 0;


	for (;;) {
		c = uart_getc();
		if (c == '\r') break;
		if ((c == '\b') && i) {
			i--;
			uart_putc(c);
			continue;
		}
		if (c >= ' ' && i < len - 1) {	/* Visible chars */
			buff[i++] = c;
			xputc(c);
		}
	}
	buff[i] = 0;
	xputc('\n');
}


static
FRESULT scan_files (
	char* path,		/* Pointer to the working buffer with start path */
	UINT* n_dir,
	UINT* n_file,
	DWORD* sz_file
)
{
	DIR dirs;
	FRESULT fr;
	int i;

	fr = f_opendir(&dirs, path);
	if (fr == FR_OK) {
		while (((fr = f_readdir(&dirs, &Finfo)) == FR_OK) && Finfo.fname[0]) {
			if (Finfo.fattrib & AM_DIR) {
				(*n_dir)++;
				i = strlen(path);
				path[i] = '/'; strcpy(path+i+1, Finfo.fname);
				fr = scan_files(path, n_dir, n_file, sz_file);
				path[i] = 0;
				if (fr != FR_OK) break;
			} else {
//				xprintf(PSTR("%s/%s\n"), path, Finfo.fname);
				(*n_file)++;
				*sz_file += Finfo.fsize;
			}
		}
	}

	return fr;
}



static
void put_rc (FRESULT rc)
{
	const prog_char *p;
	static const prog_char str[] =
		"OK\0DISK_ERR\0INT_ERR\0NOT_READY\0NO_FILE\0NO_PATH\0INVALID_NAME\0"
		"DENIED\0EXIST\0INVALID_OBJECT\0WRITE_PROTECTED\0INVALID_DRIVE\0"
		"NOT_ENABLED\0NO_FILE_SYSTEM\0MKFS_ABORTED\0TIMEOUT\0LOCKED\0"
		"NOT_ENOUGH_CORE\0TOO_MANY_OPEN_FILES\0";
	FRESULT i;

	for (p = str, i = 0; i != rc && pgm_read_byte_near(p); i++) {
		while(pgm_read_byte_near(p++));
	}
	xprintf(PSTR("rc=%u FR_%S\n"), rc, p);
}




static
void ioinit (void)
{
	MCUCR = _BV(JTD); MCUCR = _BV(JTD);	/* Disable JTAG */

	/* Start 100Hz system timer with TC0 */
	OCR0A = F_CPU / 1024 / 100 - 1;
	TCCR0A = _BV(WGM01);
	TCCR0B = 0b101;
	TIMSK0 = _BV(OCIE0A);

	sei();
}



/*-----------------------------------------------------------------------*/
/* Main                                                                  */


int main (void)
{
	char line[120];
	char *ptr, *ptr2;
	long p1, p2, p3;
	LBA_t lba;
	FRESULT fr;
	BYTE b1, *bp;
	UINT s1, s2, cnt, blen = sizeof Buff, acc_dirs, acc_files;
	DWORD ofs, sect = 0, dw, acc_size;
	static const char* const fst[] = {"", "FAT12", "FAT16", "FAT32", "exFAT"};
	FATFS *fs;
	RTC rtc;


	ioinit();				/* Initialize port settings and start system timer process */
	uart_init(115200);		/* Initialize UART driver */
	xdev_out(uart_putc);	/* Register uart_putc() to xitoa module as console output */
	xputs(PSTR("\nFatFs Module Test Monitor\n"));
	xprintf(PSTR("LFN=%S, CP=%u\n"), FF_USE_LFN ? PSTR("Enabled") : PSTR("Disabled"), FF_CODE_PAGE);
#ifdef DRV_CFC
	xprintf(PSTR("CFC ==> %u\n"), DRV_CFC);
#endif
#ifdef DRV_MMC
	xprintf(PSTR("MMC ==> %u\n"), DRV_MMC);
#endif


	if (rtc_init() && rtc_gettime(&rtc)) {		/* Initialize RTC */
		RtcOk = 1;
		xprintf(PSTR("Current time: %u/%u/%u %02u:%02u:%02u\n"), rtc.year, rtc.month, rtc.mday, rtc.hour, rtc.min, rtc.sec);
	} else {
		xputs(PSTR("RTC is not available.\n"));
	}


	for (;;) {
		xputc('>');
		ptr = line;

		get_line(ptr, sizeof line);
		switch (*ptr++) {

		case 'T' :
			while (*ptr == ' ') ptr++;

			/* Quick test space */

			break;

		case 'd' :
			switch (*ptr++) {
			case 'd' :	/* dd <pd#> [<sector>] - Dump secrtor */
				if (!xatoi(&ptr, &p1)) break;
				if (!xatoi(&ptr, &p2)) p2 = sect;
				b1 = disk_read((BYTE)p1, Buff, (DWORD)p2, 1);
				if (b1) { xprintf(PSTR("rc=%d\n"), b1); break; }
				sect = (DWORD)p2 + 1;
				xprintf(PSTR("Sector:%lu\n"), (DWORD)p2);
				for (bp=Buff, ofs = 0; ofs < 0x200; bp += 16, ofs += 16) {
					put_dump(bp, ofs, 16);
				}
				break;

			case 'i' :	/* di <pd#> - Initialize disk */
				if (!xatoi(&ptr, &p1)) break;
				xprintf(PSTR("rc=%d\n"), disk_initialize((BYTE)p1));
				break;

			case 's' :	/* ds <pd#> - Show disk status */
				if (!xatoi(&ptr, &p1)) break;
				if (disk_ioctl((BYTE)p1, GET_SECTOR_COUNT, &lba) == RES_OK) {
					xprintf(PSTR("Drive size: %lu sectors\n"), (DWORD)lba);
				}
				if (disk_ioctl((BYTE)p1, GET_BLOCK_SIZE, &p2) == RES_OK) {
					xprintf(PSTR("Erase block: %lu sectors\n"), p2);
				}
				if (disk_ioctl((BYTE)p1, MMC_GET_TYPE, &b1) == RES_OK) {
					xprintf(PSTR("Card type: %u\n"), b1);
				}
				if (disk_ioctl((BYTE)p1, MMC_GET_CSD, Buff) == RES_OK) {
					xputs(PSTR("CSD:\n")); put_dump(Buff, 0, 16);
				}
				if (disk_ioctl((BYTE)p1, MMC_GET_CID, Buff) == RES_OK) {
					xputs(PSTR("CID:\n")); put_dump(Buff, 0, 16);
				}
				if (disk_ioctl((BYTE)p1, MMC_GET_OCR, Buff) == RES_OK) {
					xputs(PSTR("OCR:\n")); put_dump(Buff, 0, 4);
				}
				if (disk_ioctl((BYTE)p1, MMC_GET_SDSTAT, Buff) == RES_OK) {
					xputs(PSTR("SD Status:\n"));
					for (s1 = 0; s1 < 64; s1 += 16) put_dump(Buff+s1, s1, 16);
				}
				if (disk_ioctl((BYTE)p1, ATA_GET_MODEL, line) == RES_OK) {
					line[40] = '\0'; xprintf(PSTR("Model: %s\n"), line);
				}
				if (disk_ioctl((BYTE)p1, ATA_GET_SN, line) == RES_OK) {
					line[20] = '\0'; xprintf(PSTR("S/N: %s\n"), line);
				}
				break;

			case 'c' :	/* Disk ioctl */
				switch (*ptr++) {
				case 's' :	/* dcs <pd#> - CTRL_SYNC */
					if (!xatoi(&ptr, &p1)) break;
					xprintf(PSTR("rc=%d\n"), disk_ioctl((BYTE)p1, CTRL_SYNC, 0));
					break;
				}
				break;
			}
			break;

		case 'b' :
			switch (*ptr++) {
			case 'd' :	/* bd <addr> - Dump R/W buffer */
				if (!xatoi(&ptr, &p1)) break;
				for (bp=&Buff[p1], ofs = p1, cnt = 32; cnt; cnt--, bp += 16, ofs += 16) {
					put_dump(bp, ofs, 16);
				}
				break;

			case 'e' :	/* be <addr> [<data>] ... - Edit R/W buffer */
				if (!xatoi(&ptr, &p1)) break;
				if (xatoi(&ptr, &p2)) {
					do {
						Buff[p1++] = (BYTE)p2;
					} while (xatoi(&ptr, &p2));
					break;
				}
				for (;;) {
					xprintf(PSTR("%04X %02X-"), (WORD)p1, Buff[p1]);
					get_line(line, sizeof line);
					ptr = line;
					if (*ptr == '.') break;
					if (*ptr < ' ') { p1++; continue; }
					if (xatoi(&ptr, &p2)) {
						Buff[p1++] = (BYTE)p2;
					} else {
						xputs(PSTR("???\n"));
					}
				}
				break;

			case 'r' :	/* br <pd#> <sector> [<n>] - Read disk into R/W buffer */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				if (!xatoi(&ptr, &p3)) p3 = 1;
				xprintf(PSTR("rc=%u\n"), disk_read((BYTE)p1, Buff, p2, p3));
				break;

			case 'w' :	/* bw <pd#> <sector> [<n>] - Write R/W buffer into disk */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				if (!xatoi(&ptr, &p3)) p3 = 1;
				xprintf(PSTR("rc=%u\n"), disk_write((BYTE)p1, Buff, p2, p3));
				break;

			case 'f' :	/* bf <n> - Fill working buffer */
				if (!xatoi(&ptr, &p1)) break;
				memset(Buff, (BYTE)p1, sizeof Buff);
				break;

			case 'l' :	/* bl <len> - Set read/write size for fr/fw command */
				if (!xatoi(&ptr, &p1) || (UINT)p1 == 0 || (UINT)p1 > sizeof Buff) break;
				blen = (UINT)p1;
				xprintf(PSTR("R/W length = %u\n"), blen);
				break;
			}
			break;

		case 'f' :
			switch (*ptr++) {

			case 'i' :	/* fi <ld#> [<mount>]- Initialize logical drive */
				if (!xatoi(&ptr, &p1) || (UINT)p1 > 9) break;
				if (!xatoi(&ptr, &p2)) p2 = 0;
				xsprintf(line, PSTR("%u:"), (UINT)p1);
				put_rc(f_mount(&FatFs[p1], line, (BYTE)p2));
				break;

			case 's' :	/* fs [<path>] - Show logical drive status */
				while (*ptr == ' ') ptr++;
				ptr2 = ptr;
				fr = f_getfree(ptr, &dw, &fs);
				if (fr) { 
					put_rc(fr); break;
				}
				xprintf(PSTR("FAT type = %s\nBytes/Cluster = %lu\nNumber of FATs = %u\n"
							 "Root DIR entries = %u\nSectors/FAT = %lu\nNumber of clusters = %lu\n"
							 "Volume start (lba) = %lu\nFAT start (lba) = %lu\nDIR start (lba,clustor) = %lu\nData start (lba) = %lu\n\n"),
						fst[fs->fs_type], (DWORD)fs->csize * 512, fs->n_fats,
						fs->n_rootdir, fs->fsize, fs->n_fatent - 2,
						(DWORD)fs->volbase, (DWORD)fs->fatbase, (DWORD)fs->dirbase, (DWORD)fs->database
				);
#if FF_USE_LABEL
				fr = f_getlabel(ptr2, (char*)Buff, (DWORD*)&p1);
				if (fr) {
					put_rc(fr); break;
				}
				xprintf(Buff[0] ? PSTR("Volume name is %s\n") : PSTR("No volume label\n"), Buff);
				xprintf(PSTR("Volume S/N is %04X-%04X\n"), (WORD)((DWORD)p1 >> 16), (WORD)(p1 & 0xFFFF));
#endif
				xputs(PSTR("..."));
				acc_size = acc_dirs = acc_files = 0;
				strcpy((char*)Buff, ptr);
				fr = scan_files((char*)Buff, &acc_dirs, &acc_files, &acc_size);
				if (fr) {
					put_rc(fr); break;
				}
				xprintf(PSTR("\r%u files, %lu bytes.\n%u folders.\n"
							 "%lu KiB total disk space.\n%lu KiB available.\n"),
						acc_files, acc_size, acc_dirs,
						(fs->n_fatent - 2) * (fs->csize / 2), dw * (fs->csize / 2)
				);
				break;

			case 'l' :	/* fl [<path>] - Directory listing */
				while (*ptr == ' ') ptr++;
				fr = f_opendir(&Dir, ptr);
				if (fr) {
					put_rc(fr); break;
				}
				p1 = s1 = s2 = 0;
				for(;;) {
					fr = f_readdir(&Dir, &Finfo);
					if ((fr != FR_OK) || !Finfo.fname[0]) break;
					if (Finfo.fattrib & AM_DIR) {
						s2++;
					} else {
						s1++; p1 += Finfo.fsize;
					}
					xprintf(PSTR("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu  %s\n"), 
								(Finfo.fattrib & AM_DIR) ? 'D' : '-',
								(Finfo.fattrib & AM_RDO) ? 'R' : '-',
								(Finfo.fattrib & AM_HID) ? 'H' : '-',
								(Finfo.fattrib & AM_SYS) ? 'S' : '-',
								(Finfo.fattrib & AM_ARC) ? 'A' : '-',
								(Finfo.fdate >> 9) + 1980, (Finfo.fdate >> 5) & 15, Finfo.fdate & 31,
								(Finfo.ftime >> 11), (Finfo.ftime >> 5) & 63,
								(DWORD)Finfo.fsize, 
								Finfo.fname);
				}
				if (fr == FR_OK) {
					xprintf(PSTR("%4u File(s),%10lu bytes total\n%4u Dir(s)"), s1, p1, s2);
					if (f_getfree(ptr, &dw, &fs) == FR_OK) {
						xprintf(PSTR(", %10luKiB free\n"), dw * fs->csize / 2);
					}
				}
				if (fr) put_rc(fr);
				break;

			case 'o' :	/* fo <mode> <name> - Open a file */
				if (!xatoi(&ptr, &p1)) break;
				while (*ptr == ' ') ptr++;
				put_rc(f_open(&File[0], ptr, (BYTE)p1));
				break;

			case 'c' :	/* fc - Close a file */
				put_rc(f_close(&File[0]));
				break;

			case 'e' :	/* fe - Seek file pointer */
				if (!xatoi(&ptr, &p1)) break;
				fr = f_lseek(&File[0], p1);
				put_rc(fr);
				if (fr == FR_OK) {
					xprintf(PSTR("fptr = %lu(0x%lX)\n"), (DWORD)File[0].fptr, (DWORD)File[0].fptr);
				}
				break;

			case 'r' :	/* fr <len> - read file */
				if (!xatoi(&ptr, &p1)) break;
				p2 = 0;
				cli(); Timer = 0; sei();
				while (p1) {
					if (p1 >= blen)	{ cnt = blen; p1 -= blen; }
					else 			{ cnt = (WORD)p1; p1 = 0; }
					fr = f_read(&File[0], Buff, cnt, &s2);
					if (fr != FR_OK) { put_rc(fr); break; }
					p2 += s2;
					if (cnt != s2) break;
				}
				cli(); s2 = Timer; sei();
				xprintf(PSTR("%lu bytes read at %lu bytes/sec.\n"), p2, s2 ? (p2 * 100 / s2) : 0);
				break;

			case 'd' :	/* fd <len> - read and dump file from current fp */
				if (!xatoi(&ptr, &p1)) break;
				ofs = File[0].fptr;
				while (p1) {
					if (p1 >= 16) {
						cnt = 16; p1 -= 16;
					} else {
						cnt = (WORD)p1; p1 = 0;
					}
					fr = f_read(&File[0], Buff, cnt, &cnt);
					if (fr != FR_OK) {
						put_rc(fr); break;
					}
					if (!cnt) break;
					put_dump(Buff, ofs, cnt);
					ofs += 16;
				}
				break;

			case 'w' :	/* fw <len> <val> - write file */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				memset(Buff, (BYTE)p2, blen);
				p2 = 0;
				cli(); Timer = 0; sei();
				while (p1) {
					if (p1 >= blen)	{ cnt = blen; p1 -= blen; }
					else 			{ cnt = (WORD)p1; p1 = 0; }
					fr = f_write(&File[0], Buff, cnt, &s2);
					if (fr != FR_OK) {
						put_rc(fr); break;
					}
					p2 += s2;
					if (cnt != s2) break;
				}
				cli(); s2 = Timer; sei();
				xprintf(PSTR("%lu bytes written at %lu bytes/sec.\n"), p2, s2 ? (p2 * 100 / s2) : 0);
				break;

			case 'v' :	/* fv - Truncate file */
				put_rc(f_truncate(&File[0]));
				break;

			case 'n' :	/* fn <old_name> <new_name> - Change file/dir name */
				while (*ptr == ' ') ptr++;
				ptr2 = strchr(ptr, ' ');
				if (!ptr2) break;
				*ptr2++ = 0;
				while (*ptr2 == ' ') ptr2++;
				put_rc(f_rename(ptr, ptr2));
				break;

			case 'u' :	/* fu <name> - Unlink a file or dir */
				while (*ptr == ' ') ptr++;
				put_rc(f_unlink(ptr));
				break;

			case 'k' :	/* fk <name> - Create a directory */
				while (*ptr == ' ') ptr++;
				put_rc(f_mkdir(ptr));
				break;
#if FF_USE_EXPAND
			case 'h':	/* fh <fsz> <opt> - Allocate contiguous block */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				fr = f_expand(&File[0], (DWORD)p1, (BYTE)p2);
				put_rc(fr);
				break;
#endif
#if FF_USE_CHMOD
			case 'a' :	/* fa <atrr> <mask> <name> - Change file/dir attribute */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				while (*ptr == ' ') ptr++;
				put_rc(f_chmod(ptr, p1, p2));
				break;

			case 't' :	/* ft <year> <month> <day> <hour> <min> <sec> <name> */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2) || !xatoi(&ptr, &p3)) break;
				Finfo.fdate = ((p1 - 1980) << 9) | ((p2 & 15) << 5) | (p3 & 31);
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2) || !xatoi(&ptr, &p3)) break;
				Finfo.ftime = ((p1 & 31) << 11) | ((p2 & 63) << 5) | ((p3 >> 1) & 31);
				while (*ptr == ' ') ptr++;
				put_rc(f_utime(ptr, &Finfo));
				break;
#endif
			case 'x' : /* fx <src_name> <dst_name> - Copy file */
				while (*ptr == ' ') ptr++;
				ptr2 = strchr(ptr, ' ');
				if (!ptr2) break;
				*ptr2++ = 0;
				while (*ptr2 == ' ') ptr2++;
				xprintf(PSTR("Opening \"%s\""), ptr);
				fr = f_open(&File[0], ptr, FA_OPEN_EXISTING | FA_READ);
				if (fr) {
					put_rc(fr); break;
				}
				xprintf(PSTR("\nCreating \"%s\""), ptr2);
				fr = f_open(&File[1], ptr2, FA_CREATE_ALWAYS | FA_WRITE);
				if (fr) {
					put_rc(fr);
					f_close(&File[0]);
					break;
				}
				xprintf(PSTR("\nCopying..."));
				cli(); Timer = 0; sei();
				p1 = 0;
				for (;;) {
					fr = f_read(&File[0], Buff, sizeof Buff, &s1);
					if (fr || s1 == 0) break;   /* error or eof */
					fr = f_write(&File[1], Buff, s1, &s2);
					p1 += s2;
					if (fr || s2 < s1) break;   /* error or disk full */
				}
				if (fr) put_rc(fr);
				cli(); s2 = Timer; sei();
				xprintf(PSTR("\n%lu bytes copied at %lu bytes/sec.\n"), p1, p1 * 100 / s2);
				f_close(&File[0]);
				f_close(&File[1]);
				break;
#if FF_FS_RPATH
			case 'g' :	/* fg <path> - Change current directory */
				while (*ptr == ' ') ptr++;
				put_rc(f_chdir(ptr));
				break;
#if FF_VOLUMES >= 2
			case 'j' :	/* fj <path> - Change current drive */
				while (*ptr == ' ') ptr++;
				put_rc(f_chdrive(ptr));
				break;
#endif
#if FF_FS_RPATH >= 2
			case 'q' :	/* fq - Show current dir path */
				fr = f_getcwd(line, sizeof line);
				if (fr) {
					put_rc(fr);
				} else {
					xprintf(PSTR("%s\n"), line);
				}
				break;
#endif
#endif
#if FF_USE_LABEL
			case 'b' :	/* fb <name> - Set volume label */
				while (*ptr == ' ') ptr++;
				put_rc(f_setlabel(ptr));
				break;
#endif
#if FF_USE_MKFS
			case 'm' :	/* fm <ld#> [<fs type> [<au_size> [<align> [<n_fats> [<n_root>]]]]] - Create filesystem */
				if (xatoi(&ptr, &p1) && (UINT)p1 < FF_VOLUMES) {
					MKFS_PARM opt, *popt = 0;

					if (xatoi(&ptr, &p2)) {
						memset(&opt, 0, sizeof opt);
						popt = &opt;
						popt->fmt = (BYTE)p2;
						if (xatoi(&ptr, &p2)) {
							popt->au_size = p2;
							if (xatoi(&ptr, &p2)) {
								popt->align = p2;
								if (xatoi(&ptr, &p2)) {
									popt->n_fat = (BYTE)p2;
									if (xatoi(&ptr, &p2)) {
										popt->n_root = p2;
									}
								}
							}
						}
					}
					xprintf(PSTR("The drive %u will be formatted. Are you sure? (Y/n)="), (WORD)p1);
					get_line(line, sizeof line);
					if (line[0] == 'Y') {
						xsprintf(line, PSTR("%u:"), (UINT)p1);
						put_rc(f_mkfs(line, popt, Buff, sizeof Buff));
					}
				}
				break;
#endif
			}
			break;
#ifdef SOUND_DEFINED
		case 'p' :	/* p <wavfile> - Play RIFF-WAV file */
			while (*ptr == ' ') ptr++;
			fr = f_open(&File[0], ptr, FA_READ);
			if (fr) {
				put_rc(fr);
			} else {
				load_wav(&File[0], "**** WAV PLAYER ****", Buff, sizeof Buff);
				f_close(&File[0]);
			}
			break;
#endif
		case 't' :	/* t [<year> <mon> <mday> <hour> <min> <sec>] */
			if (!RtcOk) break;
			if (xatoi(&ptr, &p1)) {
				rtc.year = (WORD)p1;
				xatoi(&ptr, &p1); rtc.month = (BYTE)p1;
				xatoi(&ptr, &p1); rtc.mday = (BYTE)p1;
				xatoi(&ptr, &p1); rtc.hour = (BYTE)p1;
				xatoi(&ptr, &p1); rtc.min = (BYTE)p1;
				if (!xatoi(&ptr, &p1)) break;
				rtc.sec = (BYTE)p1;
				rtc_settime(&rtc);
			}
			rtc_gettime(&rtc);
			xprintf(PSTR("%u/%u/%u %02u:%02u:%02u\n"), rtc.year, rtc.month, rtc.mday, rtc.hour, rtc.min, rtc.sec);
			break;

		case '?' :	/* Show Command List */
			xputs(PSTR(
			"[Disk contorls]\n"
			" di <pd#> - Initialize disk\n"
			" dd [<pd#> <sect>] - Dump a secrtor\n"
			" ds <pd#> - Show disk status\n"
			" dcs <pd#> - ioctl(CTRL_SYNC)\n"
			"[Buffer controls]\n"
			" bd <ofs> - Dump working buffer\n"
			" be <ofs> [<data>] ... - Edit working buffer\n"
			" br <pd#> <sect> [<num>] - Read disk into working buffer\n"
			" bw <pd#> <sect> [<num>] - Write working buffer into disk\n"
			" bf <val> - Fill working buffer\n"
			" bl <len> - Set read/write length for fr/fw command\n"
			"[Filesystem controls]\n"
			" fi <ld#> [<mount>] - Force initialized the volume\n"
			" fs [<path>] - Show volume status\n"
			" fl [<path>] - Show a directory\n"
			" fo <mode> <file> - Open a file\n"
			" fc - Close the file\n"
			" fe <ofs> - Move fp in normal seek\n"
			" fh <fsz> <opt> - Allocate a contiguous block to the file\n"
			" fd <len> - Read and dump the file\n"
			" fr <len> - Read the file\n"
			" fw <len> <val> - Write to the file\n"
			" fn <org name> <new name> - Rename an object\n"
			" fu <obj name> - Unlink an object\n"
			" fv - Truncate the file at current fp\n"
			" fk <dir name> - Create a directory\n"
			" fa <atrr> <mask> <object name> - Change object attribute\n"
			" ft <year> <month> <day> <hour> <min> <sec> <object name> - Change timestamp of an object\n"
			" fx <src file> <dst file> - Copy a file\n"
			" fg <path> - Change current directory\n"
			" fj <path> - Change current drive\n"
			" fq - Show current directory\n"
			" fm <ld#> [<fs type> [<au_size> [<align> [<n_fats> [<n_root>]]]]] - Create FAT volume\n"
			"[Misc commands]\n"
			" p <wavfile> - Play RIFF-WAVE file\n"
			" t [<year> <month> <mday> <hour> <min> <sec>] - Set/Show current time\n"
			"\n"));
			break;

		}
	}

}

